/*******************************************************************************
 * Copyright (c) 2011, 2016 Eurotech and/or its affiliates and others
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Eurotech - initial API and implementation
 *
 *******************************************************************************/
package org.eclipse.kapua.service.authentication.shiro;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.apache.shiro.ShiroException;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.pam.AuthenticationStrategy;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.realm.Realm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Kapua Shiro Authenticator.<br>
 * This authenticator provide more significantly exception message in a multi-realm configuration.<br>
 * The code is derived from the original {@link ModularRealmAuthenticator} because the <b>default Shiro implementation doesn't support detailed messages in a multirealm configuration.</b>
 *
 * since 1.0
 * 
 */
public class KapuaAuthenticator extends ModularRealmAuthenticator {
	
	private static final Logger loggger = LoggerFactory.getLogger(KapuaAuthenticator.class);

	@Override
	protected AuthenticationInfo doMultiRealmAuthentication(Collection<Realm> realms, AuthenticationToken token) {
		AuthenticationStrategy strategy = getAuthenticationStrategy();
		AuthenticationInfo aggregate = strategy.beforeAllAttempts(realms, token);
		if (loggger.isTraceEnabled()) {
			loggger.trace("Iterating through {} realms for PAM authentication", realms.size());
		}
		List<Throwable> exceptionList = new ArrayList<>();
		boolean loginSucceeded = false;
		boolean supportedRealmFound = false;
		for (Realm realm : realms) {
			aggregate = strategy.beforeAttempt(realm, token, aggregate);
			if (realm.supports(token)) {
				supportedRealmFound = true;
				loggger.trace("Attempting to authenticate token [{}] using realm [{}]", token, realm);
				AuthenticationInfo info = null;
				Throwable t = null;
				try {
					info = realm.getAuthenticationInfo(token);
					loginSucceeded = true;
				}
				catch (Throwable throwable) {
					t = throwable;
					if (loggger.isDebugEnabled()) {
						String msg = "Realm [" + realm
								+ "] threw an exception during a multi-realm authentication attempt:";
						loggger.debug(msg, t);
					}
				}
				aggregate = strategy.afterAttempt(realm, token, info, aggregate, t);
				exceptionList.add(t);
			}
			else {
				loggger.debug("Realm [{}] does not support token {}.  Skipping realm.", realm, token);
			}
		}
		//modified behavior from the ModularRealmAuthenticator to provide a more significantly exception message to the user if the login fails
		if (supportedRealmFound && !loginSucceeded) {
			//if there is no realm able to authenticate the AuthenticationToken (but at least one realm for this AuthenticationToken was found) lets check the exceptions thrown by the logins
			if (exceptionList.size()<=0) {
				//login failed and we have no exception to show so throw a ShiroException?
				//TODO move the error message to the message bundle
				throw new ShiroException("Internal Error!");
			}
			if (exceptionList.get(0) instanceof AuthenticationException) {
				throw (AuthenticationException) exceptionList.get(0);
			}
			else {
				throw new AuthenticationException(exceptionList.get(0)); 
			}
		}
		else {
			//otherwise if at least one login succeeded lets proceed with the standard ModularRealmAuthenticator
			aggregate = strategy.afterAllAttempts(token, aggregate);
		}
		return aggregate;
	}

}
